﻿using System;
using System.Collections.Generic;

namespace Kurax.BoardGames {
    public class Crypto {
        private readonly byte[] key = new byte[0x10];
        private bool header;
        private long contextStart, preCrypt, crypt, padding, pos;
        private byte[] @out, prePlain, plain;

        private static byte[] CopyMemory(byte[] arr, int arrIndex, long input) {
            if((arrIndex + 4) <= arr.Length) {
                arr[arrIndex + 3] = (byte)((input & 0xff000000L) >> 0x18);
                arr[arrIndex + 2] = (byte)((input & 0xff0000L) >> 0x10);
                arr[arrIndex + 1] = (byte)((input & 0xff00L) >> 8);
                arr[arrIndex] = (byte)(input & 0xffL);
                arr[arrIndex] = (byte)(arr[arrIndex] & 0xff);
                arr[arrIndex + 1] = (byte)(arr[arrIndex + 1] & 0xff);
                arr[arrIndex + 2] = (byte)(arr[arrIndex + 2] & 0xff);
                arr[arrIndex + 3] = (byte)(arr[arrIndex + 3] & 0xff);
            }
            return arr;
        }

        private byte[] Decipher(byte[] arrayIn, byte[] arrayKey, long offset = 0L) {
            var arr = new byte[0x18];
            var buffer2 = new byte[8];
            if(arrayIn.Length >= 8) {
                if(arrayKey.Length < 0x10)
                    return buffer2;
                var num = 0xe3779b90L;
                num &= 0xffffffffL;
                var num2 = 0x9e3779b9L;
                num2 &= 0xffffffffL;
                var input = this.getUnsignedInt(arrayIn, (int)offset, 4);
                var num4 = this.getUnsignedInt(arrayIn, ((int)offset) + 4, 4);
                var num5 = this.getUnsignedInt(arrayKey, 0, 4);
                var num6 = this.getUnsignedInt(arrayKey, 4, 4);
                var num7 = this.getUnsignedInt(arrayKey, 8, 4);
                var num8 = this.getUnsignedInt(arrayKey, 12, 4);
                for(var i = 1; i <= 0x10; i++) {
                    num4 -= (((input << 4) + num7) ^ (input + num)) ^ ((input >> 5) + num8);
                    num4 &= 0xffffffffL;
                    input -= (((num4 << 4) + num5) ^ (num4 + num)) ^ ((num4 >> 5) + num6);
                    input &= 0xffffffffL;
                    num -= num2;
                    num &= 0xffffffffL;
                }
                arr = CopyMemory(arr, 0, input);
                arr = CopyMemory(arr, 4, num4);
                buffer2[0] = arr[3];
                buffer2[1] = arr[2];
                buffer2[2] = arr[1];
                buffer2[3] = arr[0];
                buffer2[4] = arr[7];
                buffer2[5] = arr[6];
                buffer2[6] = arr[5];
                buffer2[7] = arr[4];
            }
            return buffer2;
        }

        private bool Decrypt8Bytes(IList<byte> arrayIn, long offset) {
            this.pos = 0L;
            while(this.pos <= 7L) {
                if((this.contextStart + this.pos) > (arrayIn.Count - 1))
                    return true;
                this.prePlain[(int)((IntPtr)this.pos)] = (byte)(this.prePlain[(int)((IntPtr)this.pos)] ^ arrayIn[(int)((IntPtr)((offset + this.crypt) + this.pos))]);
                this.pos += 1L;
            }
            try {
                this.prePlain = this.Decipher(this.prePlain, this.key);
            } catch {
                return false;
            }
            this.contextStart += 8L;
            this.crypt += 8L;
            this.pos = 0L;
            return true;
        }

        private byte[] Encipher(byte[] arrayIn, byte[] arrayKey, long offset = 0L) {
            var buffer = new byte[8];
            var arr = new byte[0x18];
            if(arrayIn.Length >= 8) {
                if(arrayKey.Length < 0x10)
                    return buffer;
                var num = 0L;
                var num2 = 0x9e3779b9L;
                num2 &= 0xffffffffL;
                var input = this.getUnsignedInt(arrayIn, (int)offset, 4);
                var num4 = this.getUnsignedInt(arrayIn, ((int)offset) + 4, 4);
                var num5 = this.getUnsignedInt(arrayKey, 0, 4);
                var num6 = this.getUnsignedInt(arrayKey, 4, 4);
                var num7 = this.getUnsignedInt(arrayKey, 8, 4);
                var num8 = this.getUnsignedInt(arrayKey, 12, 4);
                for(var i = 1; i <= 0x10; i++) {
                    num += num2;
                    num &= 0xffffffffL;
                    input += (((num4 << 4) + num5) ^ (num4 + num)) ^ ((num4 >> 5) + num6);
                    input &= 0xffffffffL;
                    num4 += (((input << 4) + num7) ^ (input + num)) ^ ((input >> 5) + num8);
                    num4 &= 0xffffffffL;
                }
                arr = CopyMemory(arr, 0, input);
                arr = CopyMemory(arr, 4, num4);
                buffer[0] = arr[3];
                buffer[1] = arr[2];
                buffer[2] = arr[1];
                buffer[3] = arr[0];
                buffer[4] = arr[7];
                buffer[5] = arr[6];
                buffer[6] = arr[5];
                buffer[7] = arr[4];
            }
            return buffer;
        }

        private void Encrypt8Bytes() {
            this.pos = 0L;
            while(this.pos <= 7L) {
                if(this.header)
                    this.plain[(int)((IntPtr)this.pos)] = (byte)(this.plain[(int)((IntPtr)this.pos)] ^ this.prePlain[(int)((IntPtr)this.pos)]);
                else
                    this.plain[(int)((IntPtr)this.pos)] = (byte)(this.plain[(int)((IntPtr)this.pos)] ^ this.@out[(int)((IntPtr)(this.preCrypt + this.pos))]);
                this.pos += 1L;
            }
            var buffer = this.Encipher(this.plain, this.key);
            for(var i = 0; i <= 7; i++)
                this.@out[(int)((IntPtr)(this.crypt + i))] = buffer[i];
            this.pos = 0L;
            while(this.pos <= 7L) {
                this.@out[(int)((IntPtr)(this.crypt + this.pos))] = (byte)(this.@out[(int)((IntPtr)(this.crypt + this.pos))] ^ this.prePlain[(int)((IntPtr)this.pos)]);
                this.pos += 1L;
            }
            this.plain.CopyTo(this.prePlain, 0);
            this.preCrypt = this.crypt;
            this.crypt += 8L;
            this.pos = 0L;
            this.header = false;
        }

        private long getUnsignedInt(byte[] arrayIn, int offset, int len) {
            var num = 0L;
            int num2;
            if(len > 8)
                num2 = offset + 8;
            else
                num2 = offset + len;
            for(var i = offset; i < num2; i++) {
                num = num << 8;
                num |= (uint)(arrayIn[i] & 0xff);
            }
            return ((num & (0xffffffffL)) | (num >> 0x20));
        }

        public byte[] Decrypt(byte[] arrayIn, byte[] arrayKey) {
            return this.Decrypt(arrayIn, arrayKey, 0L);
        }

        public byte[] Decrypt(byte[] arrayIn, byte[] arrayKey, long offset) {
            var buffer = new byte[0];
            if((arrayIn.Length < 0x10) || ((arrayIn.Length % 8) != 0))
                return buffer;
            if(arrayKey.Length != 0x10)
                return buffer;
            var buffer2 = new byte[offset + 8L];
            arrayKey.CopyTo(this.key, 0);
            this.crypt = this.preCrypt = 0L;
            this.prePlain = this.Decipher(arrayIn, arrayKey, offset);
            this.pos = this.prePlain[0] & 7;
            var num2 = (arrayIn.Length - this.pos) - 10L;
            if(num2 <= 0L)
                return buffer;
            this.@out = new byte[num2];
            this.preCrypt = 0L;
            this.crypt = 8L;
            this.contextStart = 8L;
            this.pos += 1L;
            this.padding = 1L;
            while(this.padding < 3L)
                if(this.pos < 8L) {
                    this.pos += 1L;
                    this.padding += 1L;
                } else if(this.pos == 8L) {
                    for(var i = 0; i < buffer2.Length; i++)
                        buffer2[i] = arrayIn[i];
                    if(!this.Decrypt8Bytes(arrayIn, offset))
                        return buffer;
                }
            var num = 0L;
            while(num2 != 0L)
                if(this.pos < 8L) {
                    this.@out[(int)((IntPtr)num)] = (byte)(buffer2[(int)((IntPtr)((offset + this.preCrypt) + this.pos))] ^ this.prePlain[(int)((IntPtr)this.pos)]);
                    num += 1L;
                    num2 -= 1L;
                    this.pos += 1L;
                } else if(this.pos == 8L) {
                    buffer2 = arrayIn;
                    this.preCrypt = this.crypt - 8L;
                    if(!this.Decrypt8Bytes(arrayIn, offset))
                        return buffer;
                }
            this.padding = 1L;
            while(this.padding <= 7L) {
                if(this.pos < 8L) {
                    if((buffer2[(int)((IntPtr)((offset + this.preCrypt) + this.pos))] ^ this.prePlain[(int)((IntPtr)this.pos)]) != 0)
                        return buffer;
                    this.pos += 1L;
                } else if(this.pos == 8L) {
                    for(var j = 0; j < buffer2.Length; j++)
                        buffer2[j] = arrayIn[j];
                    this.preCrypt = this.crypt;
                    if(!this.Decrypt8Bytes(arrayIn, offset))
                        return buffer;
                }
                this.padding += 1L;
            }
            return this.@out;
        }

        public byte[] Encrypt(byte[] arrayIn, byte[] arrayKey) {
            return this.Encrypt(arrayIn, arrayKey, 0L);
        }

        public byte[] Encrypt(byte[] arrayIn, byte[] arrayKey, long offset) {
            this.plain = new byte[8];
            this.prePlain = new byte[8];
            this.pos = 1L;
            this.padding = 0L;
            this.crypt = this.preCrypt = 0L;
            arrayKey.CopyTo(this.key, 0);
            this.header = true;
            this.pos = 2L;
            this.pos = (arrayIn.Length + 10) % 8;
            if(this.pos != 0L)
                this.pos = 8L - this.pos;
            this.@out = new byte[(arrayIn.Length + this.pos) + 10L];
            this.plain[0] = (byte)((this.Rand() & 0xf8L) | this.pos);
            for(var i = 1; i <= this.pos; i++)
                this.plain[i] = (byte)(this.Rand() & 0xffL);
            this.pos += 1L;
            this.padding = 1L;
            while(this.padding < 3L)
                if(this.pos < 8L) {
                    this.plain[(int)((IntPtr)this.pos)] = (byte)(this.Rand() & 0xffL);
                    this.padding += 1L;
                    this.pos += 1L;
                } else if(this.pos == 8L)
                    this.Encrypt8Bytes();
            var index = (int)offset;
            long length = arrayIn.Length;
            while(length > 0L)
                if(this.pos < 8L) {
                    this.plain[(int)((IntPtr)this.pos)] = arrayIn[index];
                    index++;
                    this.pos += 1L;
                    length -= 1L;
                } else if(this.pos == 8L)
                    this.Encrypt8Bytes();
            this.padding = 1L;
            while(this.padding < 9L)
                if(this.pos < 8L) {
                    this.plain[(int)((IntPtr)this.pos)] = 0;
                    this.pos += 1L;
                    this.padding += 1L;
                } else if(this.pos == 8L)
                    this.Encrypt8Bytes();
            return this.@out;
        }

        private long Rand() {
            var random = new Random();
            return (random.Next() + (random.Next() % 0x400));
        }

        public static byte[] GenerateKey() {
            var key = new byte[16];
            var random = new Random();
            random.NextBytes(key);
            return key;
        }
    }
}
